package org.jsets.fastboot.security.token;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.jsets.fastboot.security.SecurityUtils;
import org.jsets.fastboot.security.authc.AuthcRequest;
import org.jsets.fastboot.security.authc.AuthcResult;
import org.jsets.fastboot.security.cache.Cache;
import org.jsets.fastboot.security.cache.ICacheManager;
import org.jsets.fastboot.security.config.SecurityProperties;
import org.jsets.fastboot.util.StringUtils;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import com.google.common.io.BaseEncoding;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

/**
 * 令牌管理器
 *
 * @Author wangjie
 * @date 2021.06.08 23:07
 */
@Slf4j
public class TokenManagerImpl implements ITokenManager {

	private final Cache cache;
	private final SecurityProperties properties;
	
	public TokenManagerImpl(SecurityProperties properties, ICacheManager cacheManager) {
		this.properties = properties;
		this.cache = cacheManager.create(properties.getTokenCacheName());
		log.info("RedisTokenManager初始化完成");
	}

	@Override
	public AuthcResult issueAccessToken(AuthcRequest authcRequest) {
		String signKey = this.properties.getTokenSignKey();
		Long tokenExpiration = this.properties.getTokenExpiration();
		if(Objects.nonNull(authcRequest.getTokenExpiration())) {
			tokenExpiration = authcRequest.getTokenExpiration();
		}
		
		TokenStub stub = new TokenStub();
		stub.setId(StringUtils.getUUID());
		stub.setUsername(authcRequest.getUsername());
		stub.setType(authcRequest.getAuthType());
		stub.setExpiration(tokenExpiration);
		stub.setCreateTime(new Date());
		String subject = stub.getUsername();
		String token = SecurityUtils.issueJWT(stub.getId(), subject, signKey);
		log.info("issue access token[{}]", token);
		this.cache.put(stub.getId(), stub, tokenExpiration);
		AuthcResult result = new AuthcResult();
		result.setToken(token);
		result.setTokenStubId(stub.getId());
		return result;
	}
	
	@Override
	public TokenStub validateAccessToken(String accessToken) throws AuthenticationException{
		String signKey = this.properties.getTokenSignKey();
		Claims claims = null;
		try {
			String content = new String(BaseEncoding.base64Url().decode(accessToken));
			claims = Jwts.parser().setSigningKey(signKey.getBytes()).parseClaimsJws(content).getBody();
		} catch (ExpiredJwtException e) {
			log.error(e.getMessage(), e);
			throw new InsufficientAuthenticationException(this.properties.getTokenExpiredTips());
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new InsufficientAuthenticationException(this.properties.getTokenInvalidTips());
		}
		if (Objects.isNull(claims)) {
			throw new InsufficientAuthenticationException(this.properties.getTokenInvalidTips());
		}
		
		Object value = this.cache.get(claims.getId());
		if (Objects.isNull(value)) {
			throw new InsufficientAuthenticationException(this.properties.getTokenInvalidTips());
		}
		
		TokenStub stub = (TokenStub) value;
		if (stub.getExpiration() > 0) {
			this.cache.expire(claims.getId(), stub.getExpiration());
		}
		log.info("验证访问[{}]成功", accessToken);
		return stub;
	}

	@Override
	public Optional<TokenStub> getTokenStub(String id) {
		Object value = this.cache.get(id);
		if (Objects.isNull(value)) {
			return Optional.empty();
		}
		
		TokenStub stub = (TokenStub) value;
		if (stub.getExpiration() > 0) {
			this.cache.expire(id, stub.getExpiration());
		}
		return Optional.of(stub);
	}

	@Override
	public void removeTokenStub(String id) {
		this.cache.delete(id);
	}

}